#!/bin/bash -u

interval=1
opt_long=
opt_unknown=
opt_exe=
mysudo="$(dirname "$0")/mysudo"
password=

declare -a services
declare -A max_etimes proc_exe proc_comm
services=(rxc ombdsu tachikoma)
services=(test)

proc_exe[ombdsu]=/home/ombdsu/ombdsu:/home/ctf/admin/inotify-flag:/usr/bin/watch
proc_exe[rxc]=/home/rxc/rxc:/home/ctf/admin/inotify-flag:/usr/bin/watch
proc_exe[tachikoma]=/home/tachikoma/tachikoma:/home/ctf/admin/inotify-flag:/usr/bin/watch
proc_exe[test]=/bin/cat:/tmp/cat

proc_comm[ombdsu]=ombdsu:fff:watch
proc_comm[rxc]=rxc:fff:watch
proc_comm[tachikoma]=tachikoma:fff:watch
proc_comm[test]=cat

# considered long running if the elapses time since the process was started > $max_etimes[$service]
max_etimes[ombdsu]=300
max_etimes[rxc]=30
max_etimes[tachikoma]=300
max_etimes[test]=30

### log

log_generic() { fmt=$1; echo -e "$(date +%T.%N) $fmt" "${@:2}"; }
log_action() { tput setaf 2; log_generic "$@"; tput sgr0; }
log_status() { tput setaf 3; log_generic "$@"; tput sgr0; }

### getopt

usage() {
  cat <<e
Usage: ${0##*/} OPTIONS
kill long running or unknown processes
Options:
  -e, --exe         check unknown by both /proc/\$pid/{comm,exe}
  -i, --interval N  scan interval
  -l, --long        kill long
  -u, --unknown     kill unknown comm by /proc/\$pid/comm
e
  exit $1
}

set -- $(getopt -o ehi:lu -l exe,help,interval,long,unknown -- "$@")

while :; do
  case "$1" in
    -e | --exe) opt_exe=1; shift;;
    -h | --help) usage 0;;
    -i | --interval) eval interval=$2; shift 2;;
    -l | --long) opt_long=1; shift;;
    -u | --unknown) opt_unknown=1; shift;;
    --) shift; break;;
    *) break;;
  esac
done

if [[ -z $opt_unknown && -z $opt_long ]]; then
  echo no mode specified
  usage 1
fi

### main

cease() {
  local user=$1
  local pid=$2
  local comm=$3
  local exe=$4
  local reason=$5
  log_action kill "$reason": "user=$user" "pid=$pid" "etimes=$etimes" "comm=$comm" "exe=$exe"
  $mysudo -u "$user" bash -c "kill -9 $pid &>/dev/null" <<< "$password"
}

main() {
  # select existing users on this gamebox
  declare -a services2
  local services2=()
  for i in "${services[@]}"; do
    if id -u "$i" &>/dev/null; then
      log_status found service "$i"
      services2+=($i)
    fi
  done
  services=("${services2[@]}")
  unset services2

  read -srp "$USER password: " password
  echo

  while :; do
    local now
    now=$(date +%s)
    # hack, old bash may prepend a space to "${services[*]}"
    ps -u "${services[*]}" -o pid,user:20,etimes,comm -ww --no-headers | while read -r pid user etimes comm; do
      # kill long
      if [[ -n $opt_long ]]; then
        if [[ $etimes -gt ${max_etimes[$user]} ]]; then
          cease "$user" "$pid" "$comm" '' long
        fi
      fi

      # kill unknown
      if [[ -n $opt_unknown ]]; then
        IFS=: read -ra t <<< "${proc_comm[$user]}"
        local found=
        for i in "${t[@]}"; do
          if [[ $i = "$comm" ]]; then
            found=1
            break
          fi
        done
        if [[ -z $found ]]; then
          cease "$user" "$pid" "$comm" '' comm
        elif [[ -n $opt_exe ]]; then
          local exe
          exe=$("$mysudo" -u "$user" readlink "/proc/$pid/exe" <<< "$password" 2>/dev/null)
          if [[ -z $exe || $exe = '<defunct>' ]]; then
            continue
          fi
          IFS=: read -ra t <<< "${proc_exe[$user]}"
          found=
          for i in "${t[@]}"; do
            if [[ $i = "$exe" ]]; then
              found=1
              break
            fi
          done
          if [[ -z $found ]]; then
            cease "$user" "$pid" "$comm" "$exe" exe
          fi
        fi
      fi

    done
    ((delay=now+interval-$(date +%s))) || :
    if ((delay > 0)); then sleep $delay; fi
  done
}

main
